Coffee School Game Lesson Three - Using Game States

Code Editor

// Creating environment variables var playerSize = 10; var sceneWidth = 300, sceneHeight = 150; var groundHeight = 10; var playAreaHeight = (sceneHeight - groundHeight); // Create the variables to track player movement var playerVel = 0; // Stores the player's velocity var g = 0.4; // The constant acceleration cause by "gravity" // Object width! var objectWidth = 2 * playerSize; // Obstacle Counter var obstacleCounter = 0; // Sets the background colour Crafty.background("#ADD8E6"); Crafty.defineScene("Start", function() { // Introductory text Crafty.e("2D, DOM, Text") .attr({x: sceneWidth/2, y: sceneHeight/2, w: 100, h: 20}) .text("Press 'space' to begin") .css({"text-align":"center"}) // "Dummy" player Crafty.e("2D, DOM, Color") .attr({x: 30,y: 30, w: playerSize, h: playerSize}) .color("#ff0000") // Handle any keypress events .bind("KeyDown", function(event) { // If the key pressed is the space bar then move to the "Game" scene if(event.key == Crafty.keys.SPACE) { Crafty.enterScene("Game"); } }); // Dummy ground Crafty.e("2D, DOM, Color") .attr({x: 0, y: playAreaHeight, w: sceneWidth, h: groundHeight}) .color("#00ff00"); }); // Enter the start scene Crafty.enterScene("Start"); // Our "Game Over" screen Crafty.defineScene("GameOver", function(){ // The text to display the game over message Crafty.e("2D, DOM, Text") .attr({x: sceneWidth/2, y: sceneHeight/2, w: 100, h: 20}) .text("Game Over!") .textFont({size: '14px', weight: 'bold'}); // Dummy ground Crafty.e("2D, DOM, Color") .attr({x: 0, y: playAreaHeight, w: sceneWidth, h: groundHeight}) .color("#00ff00"); }); Crafty.defineScene("Game", function() { // Create the ground! Crafty.e("Solid, 2D, DOM, Color") .attr({x: 0, y: playAreaHeight, w: sceneWidth, h: groundHeight}) .color("#00ff00"); // Create our player's base entity Crafty.e("Collision, 2D, DOM, Color") // Specifying the components to add .attr({x: 30, y: 30, w: playerSize, h: playerSize}) // Specifying the dimensions and the point to draw from .checkHits("Solid") .color("#ff0000") // Specifying the colour of the rectangle .bind("EnterFrame", function() { // Binds the "EnterFrame" event to the entity if(this.y < 0) { playerVel = g; // Prevent the player from going above the game screen } else { playerVel += g; // Adds the "gravitational acceleration" to the player's velocity } this.y += playerVel; // Change the player entities y position based on the player velocity }) .bind("KeyDown", function(event) { // Binds the "KeyDown" event to our player entity if(event.key == Crafty.keys.SPACE){ // If the key is the spacebar then "flap" playerVel = -5; // Sets the player's speed and direction to go upwards } }) .bind("HitOn", function() { Crafty.enterScene("GameOver"); }) .bind("EnterFrame", function() { Crafty("Obstacle").each(function() { if(this.x < -objectWidth) { this.destroy(); } else { this.x -= 3; } }); if(obstacleCounter > 100){ obstacleCounter = 0; newObstacle(); } obstacleCounter++; }); }); function newObstacle() { var randomHeight = Math.floor((Math.random() * (sceneHeight/2)) + (sceneHeight/3)); var bottomOfTopHalf = playAreaHeight - randomHeight; var topOfBottomHalf = bottomOfTopHalf + (4 * playerSize); // Create the top half of the pipe Crafty.e("Obstacle, 2D, DOM, Color, Solid") .attr({x: sceneWidth, y: 0, w: objectWidth, h: bottomOfTopHalf}) .color("#003319"); // Create the bottom half of the pipe Crafty.e("Obstacle, 2D, DOM, Color, Solid") .attr({x: sceneWidth, y: topOfBottomHalf, w: objectWidth, h: playAreaHeight - topOfBottomHalf}) .color("#003319"); }

Preview

Console Log:

Game Lesson Three - Using Game States

Part 2: Creating the Game Over Screen and Keeping Score

Goal: Keeping track of score and displaying it to the player.

With a game over screen there is now the possibility to show the player some information about their last play. A standard piece of information to show to the player both during the game and after they lose (or complete) is what score they managed to achieve. Score in games such as these is usually dictated by how many obstacles have been cleared by the player, so we’ll use that in this example.

There are other types of information in our game which could be recorded and used to calculate score or show to the player such as the time they managed to last before losing and how many jumps they used. After this example have a go at recording and using these different bits of information to show the player more about their last play.

In the “Game Lesson Two” we used Craftys component functionality to give our obstacles a label to allow for accessing them. The component label given to the obstacles was used to move all the obstacles and detect when an obstacle was out of play and it’s entity should be destroyed.

To keep track of score we don’t need to add much to our already existing code. As it is, when an obstacle goes off screen two entities are destroyed. Because these two entities make up an obstacle, each entity deletion with the component “Obstacle” should increase the score by half a point (0.5).

To allow score to be accessed by both the “Game” scene and the “GameOver” scene we can keep track of it in a variable score at the top of our code. Each time a game starts the score should be set to 0 so that should happen at the start of the “Game” scene.

Let’s create our score variable:

var score = 0; // Create the score variable

// Start, GameOver and Game scenes would be here

Now that we have our score variable we need to reset the score at the start of each play through the game. To do that we just need to set score = 0 at the beginning of the “Game” scene.

Crafty.defineScene("Game", function() {

    score = 0; // Reset the score

    // Main game lives here
})

With this in place we can count up the score when obstacles are destroyed! To do this we just need to increase score by 0.5 when we delete each entity that makes up half of the obstacle being destroyed.

if(this.x < -objectWidth){
  score += 0.5; // Increase the score by half a point before deleting the entity
  this.destroy();
}

This results in the player’s recorded score going up by 1 point every time they clear an obstacle (it goes off the stage).

To show the player what score they achieved in their last play-through we can create a text entity and place it in the corner of the screen. This bit involves two steps:

  1. Creating the entity to display the text to the player.
  2. Updating the displayed text for the entity each frame.

Creating the entity which displays text to the player follows the same process that we used for the text in the start screen and the end screen. The values shown in the code snippet below are just an example of the sort of values you can use, so feel free to play about and put the text where on the screen you think it should be!

// score reset is here

Crafty.e("2D, DOM, Text")
  .attr({x: 5,
         y: 10,
         w: 100,
         h: 20})
  .text("Score: " + score)
  .textFont({size: '14px', weight: 'bold'});

// Rest of main game lives here

There’s a problem with just leaving the code at that, as Crafty entities don’t update their text property when variables they use change. This means that we have to update the score text ourselves! One way of doing this is binding the “EnterFrame” event to the entity that we’re using and updating the text each game loop:

Crafty.e("2D, DOM, Text")
  .attr({x: 5,
         y: 10,
         w: 100,
         h: 20})
  .text("Score: " + score)
  .textFont({size: '14px', weight: 'bold'})
  .bind("EnterFrame", function() {
    this.text("Score: " + score); // Here we're just accessing the entities .text property and using it to update the displayed text
  });

Now we have an updating score that allows the player to see how well they’re doing in their play-through! However, if the player loses then they won’t be able to see what score they achieved so we should also show it to them on the game over screen.

Showing the player their score once the game is over is a simple matter of changing the .text property of the entity in the “GameOver” scene because we made our score globally accessible:

// Our text element in the "GameOver" scene
Crafty.e("2D, DOM, Text")
  .attr({x: sceneWidth, y: sceneHeight/2, w: 100, h: 20})
  .text("Game Over! Your score was: " + score)
  .textFont({size: '14px', weight: 'bold'});

With that change our game now allows the player to keep track of how well they’re doing while playing, while also letting them see how well they did once the game ends. But wait, what if we want to allow the player to play again without refreshing the page?

All we need to do to allow this is to move some of our game variables and bind an extra KeyDown event (within the “GameOver” scene). Moving the playerVel declaration and the obstacleCounter declaration into the game loop means that when the player starts another play through the game variables are reset.

Moving the variable declarations:

  1. Remove var playerVel = 0; and var obstacleCounter = 0; from the top of our JavaScript.
  2. Declare them at the top of our “Game” scene.

This code snippet illustrates the desired placing for the variables to work as intended:

Crafty.defineScene("Game", function() {
  score = 0; // this is where we reset the player score

  var playerVel = 0; // Making the players velocity only accessible within the game scene - while also resetting it so you don't get unintended behaviour
  var obstacleCounter = 0; // making the obstacle counter accessible only within the game scene - also resetting it so that obstacles are generated as intended

  // Here be game logic and entities!
});

Next, letting the player have another play through from the game over screen. To let the player do this we can just bind a KeyDown to our text entity in the “GameOver” scene, using the Crafty.enterScene function.

// Our "GameOver" text entity
Crafty.e("2D, DOM, Text")
  .attr({x: sceneWidth/2, y: sceneHeight/2, w: 100, h: 20})
  .text("Game Over! Your score was: " + score)
  .textFont({size: '14px', weight: 'bold'})
  // Here we're binding the KeyDown event
  .bind("KeyDown", function(event){
    // If the player presses space, go to the game scene
    if(event.key == Crafty.keys.SPACE){
      Crafty.enterScene("Game"); // Go back into the "Game" scene.
    }
  });

This lets the player start another play through from the game over screen with a fresh game state. With that last change you’ve just finished a fully functioning “flappy bird” style game!